package models

import (
	"fmt"
	"gitee.com/enzolwb/my-web-scaffold/pkg/setting"
	"github.com/casbin/casbin"
	gormadapter "github.com/casbin/gorm-adapter"
	"github.com/go-ini/ini"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
	"log"
	"time"
)

// GORM
var db *gorm.DB

// Enforcer 权限
var Enforcer *casbin.Enforcer

type Model struct {
	ID         int `gorm:"primary_key" json:"id"`
	CreatedOn  int `json:"created_on"`
	ModifiedOn int `json:"modified_on"`
	DeletedOn  int `json:"deleted_on"`
}

type Database struct {
	Type            string
	User            string
	Password        string
	Host            string
	Name            string
	TablePrefix     string
	MaxConnLifeTime int
	MaxIdleConns    int
	MaxOpenConns    int
}

var DatabaseSetting = &Database{}

func Setup(confPath string) {
	//加载配置
	Cfg, err := ini.Load(confPath)
	if err != nil {
		log.Fatalf("Fail to parse '%d': %v", confPath, err)
	}
	err = Cfg.Section("database").MapTo(DatabaseSetting)
	if err != nil {
		log.Fatalf("Cfg.MapTo DatabaseSetting err: %v", err)
	}

	var (
		dbType, dbName, user, password, host, tablePrefix string
	)

	dbType = DatabaseSetting.Type
	dbName = DatabaseSetting.Name
	user = DatabaseSetting.User
	password = DatabaseSetting.Password
	host = DatabaseSetting.Host
	tablePrefix = DatabaseSetting.TablePrefix

	dns := fmt.Sprintf(
		"%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local",
		user,
		password,
		host,
		dbName)
	db, err = gorm.Open(dbType, dns)

	if err != nil {
		log.Println(err)
	}

	gorm.DefaultTableNameHandler = func(db *gorm.DB, defaultTableName string) string {
		if defaultTableName == "casbin_rule" {
			return defaultTableName
		}
		return tablePrefix + defaultTableName
	}
	//替换模型中的create | update 的钩子方法
	db.Callback().Create().Replace(
		"gorm:update_time_stamp", updateTimeStampForCreateCallback)
	db.Callback().Update().Replace(
		"gorm:update_time_stamp", updateTimeStampForUpdateCallback)

	db.SingularTable(true)
	err = db.DB().Ping()
	if err != nil {
		log.Fatal(err)
	}
	if setting.AppSetting.Debug == 1 {
		db.LogMode(true)
	}
	db.DB().SetMaxIdleConns(DatabaseSetting.MaxIdleConns)
	db.DB().SetMaxOpenConns(DatabaseSetting.MaxOpenConns)
	db.DB().SetConnMaxLifetime(time.Duration(DatabaseSetting.MaxConnLifeTime) * time.Second)

	//RBAC casbin 的启动
	adapter := gormadapter.NewAdapterByDB(db)
	if err != nil {
		log.Fatal(err)
	}

	Enforcer = casbin.NewEnforcer("./conf/rbac_models.conf", adapter)

	//从DB加载策略
	Enforcer.LoadPolicy()
}

func CloseDB() {
	defer db.Close()
}

// updateTimeStampForCreateCallback will set `CreatedOn`, `ModifiedOn` when creating
func updateTimeStampForCreateCallback(scope *gorm.Scope) {
	if !scope.HasError() { //检查是否有含有错误（db.Error）
		nowTime := time.Now().Unix()
		//通过 scope.Fields() 获取所有字段，判断当前是否包含所需字段
		if createTimeField, ok := scope.FieldByName("CreatedOn"); ok {
			if createTimeField.IsBlank { //判断该字段的值是否为空
				createTimeField.Set(nowTime)
			}
		}

		if modifyTimeField, ok := scope.FieldByName("ModifiedOn"); ok {
			if modifyTimeField.IsBlank {
				modifyTimeField.Set(nowTime)
			}
		}
	}
}

// updateTimeStampForUpdateCallback will set `ModifyTime` when updating
func updateTimeStampForUpdateCallback(scope *gorm.Scope) {
	//假设没有指定 update_column 的字段，我们默认在更新回调设置 ModifiedOn 的值 用于特例
	if _, ok := scope.Get("gorm:update_column"); !ok {
		scope.SetColumn("ModifiedOn", time.Now().Unix())
	}
}
